#include "../../Epsilon/strings.h"

extern int GameError (int reason);

#include "textures.h"
#include "effect.h" // Underlight::EffectManager
#include "states.h"
#include "../console.h" // eTB_Printf


#include "../../Epsilon/file.h"
#include "../../Epsilon/debug.h"

#include "../../Epsilon/Epsilon3D/image.h"
#include "../../Epsilon/Epsilon3D/rendercontext.h"

extern void swap_scanlines (unsigned char* data, int width, int height, int bpp);
extern void swap_columns   (unsigned char* data, int width, int height, int bpp);

/// Rotate the image 270 degrees
static unsigned char*
Unrotate (unsigned char* data, unsigned char* dest, short width, short height)
{
  unsigned char* d   = dest;
  unsigned char* tmp = new unsigned char [width * height];

  #define TEXEL_OUT(x,y) (((y) * width) + ((x)))
  #define TEXEL_IN(x,y)  (((y) * height) + ((x)))

  for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
      tmp [width * height - TEXEL_OUT (x,y) - 1] = data [TEXEL_IN (y,x)];
    }
  }

  memcpy (dest, tmp, width * height);

  delete [] tmp;

#undef TEXEL

  swap_columns   (dest, width, height, 1);
  swap_scanlines (dest, width, height, 1);

  return dest;
}

/// Convert (and scale, if needed) an RGB image to an RGBA image.
///
///  * Fills the alpha channel with 0xff.
void 
ScaleRGB4 (const void* src,       void* dst,
           int         src_width, int   src_height,
           int         dst_width, int   dst_height,
           int         bpp,       int   pitch = -1)
{
  unsigned char *d, *s;
  int i, j,
      x, y,
      c;

  float ratio_w = (float) src_width  / dst_width;
  float ratio_h = (float) src_height / dst_height;

  if (pitch == -1)
    pitch = dst_width;

  if (! ((src_width && src_height) &&
         (dst_width && dst_height))) {
    Assert (false, _T ("Invalid dimensions passed to ScaleRGB..."));
    return;
  }

  for (y = 0; y < dst_height; y++) {
    j = (int)(y * ratio_h);
    d = (unsigned char *)dst + pitch * y;

    for (x = 0; x < dst_width; x++) {
      i = (int)(x * ratio_w);
      s = ((unsigned char *)src) + ((j * src_width) + i) * bpp;

      for (c = 0; c < bpp; c++) {
        *d++ = *s++;
      }

      *d++ = 0xff;
    }
  }
}

void 
ScaleRGBA (const void* src,       void* dst,
           int         src_width, int   src_height,
           int         dst_width, int   dst_height,
           int         bpp,       int   pitch = -1)

// *** TODO: ColorRGB    alpha_mask = 0x000000
{
  unsigned char *d, *s;
  int i, j,
      x, y,
      c;

  bool add_byte;

  float ratio_w = (float) src_width  / dst_width;
  float ratio_h = (float) src_height / dst_height;

  if (pitch == -1)
    pitch = dst_width;

  if (! ((src_width && src_height) &&
         (dst_width && dst_height))) {
    Assert (false, _T ("Invalid dimensions passed to ScaleRGBA..."));
    return;
  }

  Assert (bpp >= 3 && bpp <= 4, _T ("Invalid pixel size for ScaleRGBA..."));

  // Add a byte if bpp == 3
  add_byte = (bpp == 3);

  for (y = 0; y < dst_height; y++) {
    j = (int)(y * ratio_h);
    d = (unsigned char *)dst + pitch * y;

    for (x = 0; x < dst_width; x++) {
      i = (int)(x * ratio_w);
      s = ((unsigned char *)src) + ((j * src_width) + i) * bpp;

      // Remove the stupid pink alpha mask
      if (*(s + 2) == 0xff &&
          *(s + 1) == 0x00 &&
          *(s)     == 0xff) {
        *(s + 2) = 0x00;
        *(s + 1) = 0x00;
        *(s)     = 0x00;
      }

      for (c = 0; c < bpp; c++) {
        *d++ = *s++;
      }

      // This is a 3 bpp image, the alpha component is either ON or OFF,
      //  depending on the value of the corresponding RGB pixel.
      if (add_byte) {
        if (*(s - 3) == 0x00 &&
            *(s - 2) == 0x00 &&
            *(s - 1) == 0x00) {
          *d++ = 0x00;  // Transparent
        } else {
          *d++ = 0xff;  // Opaque
        }
      }
    }
  }
}

typedef unsigned int uint;

#undef log2
static uint
log2 (uint val)
{
  uint r = 0;

  while (val >>= 1)
    r++;

  return r;
}


namespace Underlight
{
  TextureManager  tex_manager;
  TextureManager* textures = &tex_manager;

TextureManager::~TextureManager (void)
{
  if (file != NULL) {
    delete file;
    file = NULL;
  }

  if (texture_headers != NULL) {
    delete [] texture_headers;
    texture_headers = NULL;
  }

  if (flat_headers != NULL) {
    delete [] flat_headers;
    flat_headers = NULL;
  }
}

bool
TextureManager::LoadTextureFile (const TCHAR* filename)
{
  if (! effects->VEBytes ()) {
    effects->LoadEffectFile (_T ("Effects.rlm"));
  }

  TextureFileHeader header;

  if (! file)
    file = new eDiskFile ();
    ////file = new eMemoryMappedFile ();

  eStringA _filename (filename);
  // Open texture file
  if (! file->open (_filename, _T ("rb")))
  {
    GAME_ERROR (IDS_NO_OPEN_TEXTURE);
    return false;
  }

  // Read texture file header
  if ((file->read (&header, sizeof (TextureFileHeader), 1)) != 1)
  {
    GAME_ERROR (IDS_NO_READ_TEXTURE_FILE_HDR);
    return false;
  }

  num_textures = header.num_textures;
  num_flats    = header.num_flats;

  // Read texture headers
  texture_headers = new TextureHeader [num_textures];
  if (file->read (texture_headers, sizeof (TextureHeader), num_textures) != (UINT)num_textures)
  {
    //eTB_Printf("num textures: %d\n",num_textures);
    GAME_ERROR (IDS_NO_READ_TEXTURE_HDR);
    return false;
  }

  // Seek to flat headers
  if (! file->seek (texture_headers    [num_textures - 1].file_position +
                      (texture_headers [num_textures - 1].height *
                       texture_headers [num_textures - 1].width),
                       eFileSeekOp::Set))
  {
    GAME_ERROR (IDS_NO_SEEK_READ_FLAT_HDR);
    return false;
  }

  // Read flat headers
  flat_headers = new FlatHeader [num_flats];
  if (file->read (flat_headers, sizeof (FlatHeader), num_flats) != (UINT)num_flats)
  {
    //eTB_Printf("num flats: %d\n",num_flats);
    GAME_ERROR (IDS_NO_READ_FLAT_HDR);
    return false;
  }

  int i, id;

  //eTB_Printf("num textures: %d\n",num_textures);

  for (i = 0; i < num_textures; i++)
  {
    id = texture_headers [i].id;

    texture_info [id].image   = NULL;
    texture_info [id].w       = texture_headers [i].height;
    texture_info [id].h       = texture_headers [i].width;
    texture_info [id].palette = texture_headers [i].palette;
  }

  for (i = 0; i < num_flats; i++)
  {
    id = flat_headers [i].id;

    flat_info [id].image   = NULL;
    flat_info [id].w       = flat_headers [i].height;
    flat_info [id].h       = flat_headers [i].width;
    flat_info [id].palette = flat_headers [i].palette;
  }

  return true;
}

TextureHeader*
TextureManager::GetTextureHeader (id_t tex_id)
{
  for (int i = 0; i < num_textures; i++) {
    if (texture_headers [i].id == tex_id) {
      return &texture_headers [i];
    }
  }

  return NULL;
}

static bool
SaveTGAFile ( const TCHAR*         filename,
              const short int&     width,
              const short int&     height,
              const unsigned char& bits,
              unsigned char*       imageData )
{
  unsigned char Unused000;  // Garbage collector (1-byte)
  short int     Unused001;  // Garbage collector (2-bytes)
  unsigned char imageType;  // Type of image we're writing
  int           colorMode;  // Color mode
  unsigned char colorSwap;  // Swap buffer
  int           imageIndex; // Index ID
  unsigned char bitDepth;   // Bit depth
  long          imageSize;  // Image data size
  FILE*         fTGA;       // Image file

  // Create our image
  if (! (fTGA = _tfopen (filename, _T ("wbS"))))
  {
    fclose (fTGA);
    return false;
  }

  switch (bits) {
    case 32:
      imageType = 3;  /* RGBA (BGRA) - Uncompressed */
      bitDepth  = 32;
      colorMode = 4;  /* 32-Bit RGBA */
      break;
    default:
    case 24:
      imageType = 2;  /* RGB (BGR) - Uncompressed */
      bitDepth  = 24;
      colorMode = 3;  /* 24-Bit RGB */
      break;
    case 16:
      imageType = 2;  /* RGB (BGR) - Uncompressed */
      bitDepth  = 16;
      colorMode = 2;  /* 16-Bit RGB */
      break;
  }

  Unused000 = NULL; // Garbage!
  Unused001 = NULL; // Garbage!

  // Write garbage (No Extra Header, No Compression)
  fwrite (&Unused000, sizeof (unsigned char),  1, fTGA);
  fwrite (&Unused000, sizeof (unsigned char),  1, fTGA);

  // Write imageType
  fwrite (&imageType, sizeof (unsigned char),  1, fTGA);

  // Write more garbage (No Compression)
  fwrite (&Unused001, sizeof (short int),      1, fTGA);
  fwrite (&Unused001, sizeof (short int),      1, fTGA);
  fwrite (&Unused000, sizeof (unsigned char),  1, fTGA);
  fwrite (&Unused001, sizeof (short int),      1, fTGA);
  fwrite (&Unused001, sizeof (short int),      1, fTGA);

  // Write image dimensions
  fwrite (&width,    sizeof (short int),       1, fTGA);
  fwrite (&height,   sizeof (short int),       1, fTGA);
  fwrite (&bitDepth, sizeof (unsigned char),   1, fTGA);

  // Write more garbage (No Flags)
  fwrite (&Unused000, sizeof (unsigned char),  1, fTGA);

  // Calculate image size
  imageSize = width * height * colorMode;

  // Change image data from RGB to BGR
  for (imageIndex = 0; imageIndex < imageSize; imageIndex += colorMode)
  {
    colorSwap = imageData [imageIndex];
    imageData [imageIndex]     = imageData [imageIndex + 2];
    imageData [imageIndex + 2] = colorSwap;
  }

  // Write the image data
  fwrite (imageData, sizeof (unsigned char), imageSize, fTGA);

  // Close the file
  fclose (fTGA);

  return true;
}

BITMAPINFO_4DX*
TextureManager::LoadTexture (id_t tex_id)
{
  BITMAPINFO_4DX* tex = &texture_info [tex_id];

  if (tex->image == NULL) {
    int size = (tex->w * tex->h * 3);  // 3 bpp for textures
    tex->image = NULL;
    texture_bytes += size;

    effects->LoadEffectPalette (tex->palette);

    TextureHeader* header = GetTextureHeader (tex_id);

    if ((! header) || (! file->seek (header->file_position, eFileSeekOp::Set)))
    {
      ::printf ("Could not find texture: %d\n", tex_id);

      //delete [] texture_info [tex_id].address;
      GAME_ERROR (IDS_NO_SEEK_FIND_TEXTURE);
      return NULL;
    }

    unsigned char* data = new unsigned char [tex->w * tex->h];

    //eTB_Printf("reading visual effect %d\n",i);
    if ((file->read (data, (tex->w * tex->h), 1)) != 1)
    {
      GAME_ERROR (IDS_NO_READ_TEXTURE_BIT);
      return NULL;
    }

    Unrotate (data, data,
               tex->h, tex->w);

    ePalettedImage* img =
      new ePalettedImage (tex->h, tex->w, eImagePixelFormats::RGB, 1, data,
                            effects->GetPalette (tex->palette)->color);

    tex->image = img->Convert (eImagePixelFormats::RGB, 3);

    // Generate a pseudo normal map...
    ePalettedImage* dot3 =
      new ePalettedImage (tex->h, tex->w, eImagePixelFormats::RGB, 1, data,
                            effects->GetPalette (tex->palette)->color);

    tex->normal_map = dot3->Convert (eImagePixelFormats::RGB, 3);
    tex->normal_map->GenerateDOT3 ();

#ifdef HAS_D3D
    // Don't swap the color channels if we're not using Direct3D.
    if (e3dDirect3D::IsDevicePresent ())
      tex->image->Convert (eImagePixelFormats::BGR, 3);
#endif

    // Textures are converted to 32-bit for rendering.
    //texture_bytes += (tex->h * tex->w * 4);

    delete img;
    delete dot3;
    //delete unpaletted;
  }

  return tex;
}

FlatHeader*
TextureManager::GetFlatHeader (id_t flat_id)
{
  for (int i = 0; i < num_flats; i++) {
    if (flat_headers [i].id == flat_id) {
      return &flat_headers [i];
    }
  }

  return NULL;
}

BITMAPINFO_4DX*
TextureManager::LoadFlat (id_t flat_id)
{
  BITMAPINFO_4DX* flat = &flat_info [flat_id];

  if (flat->image == NULL) {
    int size = flat->w * flat->h * 3;
    flat->image = NULL;
    flat_bytes += size;

    effects->LoadEffectPalette (flat->palette);

    FlatHeader* header = GetFlatHeader (flat_id);

    if ((! header) || (! file->seek (header->file_position, eFileSeekOp::Set)))
    {
      //delete [] flat_info [flat_id].address;
      GAME_ERROR (IDS_NO_SEEK_FIND_FLAT);
      return NULL;
    }

    unsigned char* data = new unsigned char [size / 3];

    //eTB_Printf("reading visual effect %d\n",i);
    if ((file->read (data, (flat->w * flat->h), 1)) != 1)
    {
      GAME_ERROR (IDS_NO_READ_FLAT_BIT);
      return NULL;
    }

    // Flat textures are NOT pre-rotated.
    /*
    Unrotate    (data, data,
                  flat->h, flat->w);
    */

    ePalettedImage* img =
      new ePalettedImage (flat->h, flat->w, eImagePixelFormats::RGB, 1, data,
                            effects->GetPalette (flat->palette)->color);

    flat->image = img->Convert (eImagePixelFormats::RGB, 3);

    // Generate a pseudo normal map...
    ePalettedImage* dot3 =
      new ePalettedImage (flat->h, flat->w, eImagePixelFormats::RGB, 1, data,
                            effects->GetPalette (flat->palette)->color);

    flat->normal_map = dot3->Convert (eImagePixelFormats::RGB, 3);
    flat->normal_map->GenerateDOT3 ();

#ifdef HAS_D3D
    // Don't swap the color channels if we're not using Direct3D.
    if (e3dDirect3D::IsDevicePresent ())
      flat->image->Convert (eImagePixelFormats::BGR, 3);
#endif

    // Textures are converted to 32-bit for rendering.
    //flat_bytes += (flat->h * flat->w * 4);

    delete img;
    delete dot3;
    //delete unpaletted;
  }

  return flat;
}
};

#include "../../Epsilon/Epsilon3D/rendercontext.h"
#include "../../Epsilon/Epsilon3D/image.h"
#include "../../Epsilon/Epsilon3D/texture.h"

bool
TextureFrom4DX ( BITMAPINFO_4DX*         pTex_4DX,
                 e3dTexture**            pTex,
                 Underlight::TextureType type )
{
  if (pTex_4DX == NULL || pTex_4DX->image == NULL)
    return false;

  *pTex = cli_parse::states->render_context->CreateTexture2D (pTex_4DX->image, true);

  if (*pTex) {
    delete pTex_4DX->image;
    pTex_4DX->image = NULL;

    return true;
  }

  return false;
}

bool
NormalMapFrom4DX ( BITMAPINFO_4DX*         pTex_4DX,
                   e3dTexture**            pTex,
                   Underlight::TextureType type )
{
  if (pTex_4DX == NULL || pTex_4DX->normal_map == NULL)
    return false;

  *pTex = cli_parse::states->render_context->CreateTexture2D (pTex_4DX->normal_map, true);

  if (*pTex) {
    delete pTex_4DX->normal_map;
    pTex_4DX->normal_map = NULL;

    return true;
  }

  return false;
}



bool
SaveTGAFile ( const TCHAR*         filename,
              const short int&     width,
              const short int&     height,
              const unsigned char& bits,
              unsigned char*       imageData );

bool
Save4DXToFile ( BITMAPINFO_4DX*         pTex_4DX,
                const TCHAR* szFileName,
                Underlight::TextureType type )
{
  int width, height;

  width  = pTex_4DX->h;
  height = pTex_4DX->w;

  /*
  Underlight::effects->LoadEffectPalette (pTex_4DX->palette);

    unsigned char* out = new unsigned char [width * height * 3];
      ProcessPalettedImage (pTex_4DX->address, out,
                            height, width,
                            Underlight::effects->GetPalette (pTex_4DX->palette),
                                1);

      //RGBtoBGR (out, out, width, height, 3);
  */

////  SaveTGAFile (szFileName, width, height, 24, pTex_4DX->address);

  //delete [] out;

  eTB_Printf (_T ("%s : (%d x %d)\n"), szFileName, width, height);
  /*
  if (type == Underlight::VisualEffect) {
    // Underlight uses non-power of two textures for visual effects,
    //
    //  * Round the width and height of visual effect textures UP to
    //    the next largest power of two. We will rescale the texture
    //    later on.
    width  = 2 << log2 (width);
    height = 2 << log2 (height);
  }

  if (FAILED (D3DXCreateTexture ( e3dDirect3D::GetDev (),
                                    width, height,
                                      D3DX_DEFAULT,
                                        D3DUSAGE_AUTOGENMIPMAP,
                                          D3DFMT_A8R8G8B8,
                                            D3DPOOL_MANAGED,
                                              pTex_D3D))) {
    return false;
  } else {
    RGBtoBGR ( pTex_4DX->address, pTex_4DX->address,
               pTex_4DX->h,       pTex_4DX->w,
               3 );

    LPDIRECT3DTEXTURE9 texture = *pTex_D3D;

    D3DSURFACE_DESC surfDesc;
    texture->GetLevelDesc (0, &surfDesc);

    D3DLOCKED_RECT lockedRect;
    texture->LockRect (0, &lockedRect, NULL, 0);
    {
      if (type == Underlight::VisualEffect) {
        ScaleRGBA ( pTex_4DX->address, lockedRect.pBits,
                    pTex_4DX->h,       pTex_4DX->w,
                    surfDesc.Width,    surfDesc.Height,
                    3,                 lockedRect.Pitch );
      } else {
        ScaleRGB4 ( pTex_4DX->address, lockedRect.pBits,
                    pTex_4DX->h,       pTex_4DX->w,
                    surfDesc.Width,    surfDesc.Height,
                    3,                 lockedRect.Pitch );
      }
    }
    texture->UnlockRect (0);
  }
  */
  
  /*
  if (pTex_4DX->address != (unsigned char *)-1)
    delete [] pTex_4DX->address;

  pTex_4DX->address = (unsigned char *)-1;
  */

  return true;
}
